
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
const Cc = Components.classes;
const Ci = Components.interfaces;

function MementoState() {
	this.allClients = new Array();
	this.globalId = 0;
	this.firstCall = 1;
	this.nowDate = new Date();
	
	this.prefs = Cc["@mozilla.org/preferences-service;1"]
	                .getService(Ci.nsIPrefService)
	                .getBranch("extensions.memento.");
	this.prefs.QueryInterface(Ci.nsIPrefBranch2);

	// determine locale so we can format date the same way
	this.locale = 'en-US';
	
	var cprefs = Cc["@mozilla.org/preferences-service;1"]
	                .getService(Ci.nsIPrefService)
	                .getBranch('general.useragent.');
	try {
		this.locale = cprefs.getCharPref('locale')
	} catch (e) {}
}

// wrapper functions for tab events
function MementoFox_MementoState_onTabSelected(e) {
	return memState.onTabSelected(e);
}

function MementoFox_MementoState_onTabClosed(e) {
	return memState.onTabClosed(e);
}


MementoState.prototype = {
		
		allClients: [],
		globalId: 0,
		firstCall: 1,
		nowDate: null,
		prefs: null,
		
		initialize: function() {
			window = this.getCurrentWindow();
			w = this.getMainWindow(window);
			w.gBrowser.tabContainer.addEventListener("TabSelect", MementoFox_MementoState_onTabSelected, false);
			w.gBrowser.tabContainer.addEventListener("TabClose", MementoFox_MementoState_onTabClosed, false);
		},

		// Log to Error Console
		log: function(message) {
		    var consoleService = Cc["@mozilla.org/consoleservice;1"]
		                             .getService(Ci.nsIConsoleService);
		    consoleService.logStringMessage("MementoFox -- "+message);
		},
		    
		// Tab/Window related functions

		getCurrentWindow: function() {
		    // Get current window
		    var wm = Cc["@mozilla.org/appshell/window-mediator;1"]
		                   .getService(Ci.nsIWindowMediator);
		    var recentWindow = wm.getMostRecentWindow("navigator:browser");
		    return recentWindow;
		},

		getMainWindow: function(window) {
		    if (window == null) {
		    	return null;
		    }
			var webNavigation = window.QueryInterface(Ci.nsIInterfaceRequestor)
		        .getInterface(Ci.nsIWebNavigation)
		    var rootTreeItem = webNavigation.QueryInterface(Ci.nsIDocShellTreeItem)
		        .rootTreeItem
		    var mainWindow = rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor)
		        .getInterface(Ci.nsIDOMWindow);
		    return mainWindow;
		},

		getClientForWindow: function(window) {
		    var mainWindow = this.getMainWindow(window);
		    if (mainWindow == null) {
		    	return null;
		    }
		    if (mainWindow.gBrowser == undefined) {
		    	return null;
		    }
		    var browser = mainWindow.gBrowser.mCurrentBrowser;
		    if (browser == null) {
		    	 return null;
		    }
		    var client = this.getClientForBrowser(browser.contentWindow);
		    return client;
		},
		              
		getClientForBrowser: function(browser) {	
			for (var x=0, tp; tp = this.allClients[x]; x++) {
				if (tp[0] == browser) {
					return tp[1];
				}
			}
			client = new memState.Client(browser);	
			this.allClients.push(new Array(browser, client));
			if (this.firstCall) {
				this.firstCall = 0;
				this.initialize();
			}
		  	return client;
		},

		getClientForDocument: function(doc) {
			for (var i=0,cl; cl = this.allClients[i]; i++) {
				if (dv == cl[1].document) {
					return cl[1];
				}
			}
			return null;
		},

		getCurrentUri: function() {
		    var recentWindow = this.getCurrentWindow();
		    var url = recentWindow ? recentWindow.content.document.location.href : null;
		    return url;
		},

		onTabSelected: function(e) {
			// update menubar
		    client = this.getClientForBrowser(e.target.linkedBrowser.contentWindow);
		    client.switchTo();
		},

		onTabClosed: function(e) {
			// get from tab to contentWindow which we key off of
			client = this.getClientForBrowser(e.target.linkedBrowser.contentWindow);
			// destroy client
			for (var i=0, cl; cl=this.allClients[i]; i++) {
				if (client.id == cl[1].id) {
					this.allClients.splice(i, 1);
					break
				}
			}
		},

		refreshMenubar: function() {
			// prefs have changed on a display element, so refresh
			window = this.getCurrentWindow();
			if (window == null) {
				return;
			}
			w = this.getMainWindow(window)
			client = this.getClientForWindow(w);			

			prefs = this.prefs;
			mb = window.document.getElementById('mementofox-toolbar');
			if (prefs.getBoolPref('hideToolbarWhenInactive') && (!client || !client.enabled)) {
				mb.hidden = true;
			} else {
				mb.hidden = false;
			}
			
			sl = window.document.getElementById('targetDateSlideBox');
			pk = window.document.getElementById('targetDatePicker');
			tb = window.document.getElementById('targetDateTextBox');
			rdl = window.document.getElementById('reqDateLabel');
			
			if (prefs.getBoolPref('hideWhenInactive') && (!client || !client.enabled) ) {
				sl.hidden = true;
				pk.hidden = true;
				tb.hidden = true;
				rdl.hidden = true;
			} else {
				rdl.hidden = false;
				sl.hidden = !prefs.getBoolPref("displaySlider");
				pk.hidden = !prefs.getBoolPref("displayPicker");
				tb.hidden = !prefs.getBoolPref('displayText');
			}	
		},
		
		Client: function() {
			this.enabled = false;
			this.datetime = null;
			this.actualDatetime = null;
			this.lockTime = false;

			this.requests = new Array();
			
			this.requestUri = null;
			this.originalUri = null;
			this.requestTimeGateUri = null;
			this.requestMementoUri = null;
			this.document = null;
			this.warningString = '';
			
			this.first_datetime = null;
			this.last_datetime = null;
			this.next_datetime = null;
			this.prev_datetime = null;
			
			this.startDate = new Date(1996,0,0);
			this.dateDiff = memState.nowDate.getTime() - this.startDate.getTime();
			this.nowDateStr = this.dateToYYYYMMDD(memState.nowDate);
			
			memState.globalId += 1;
			this.id = memState.globalId;
			memState.log("Built client: " + this.id);
		}
}


var memState = new MementoState();
memState.log('created MementoState!');


memState.Client.prototype = {

		switchTo: function() {
			this.ui_setEnabled();
			this.ui_setSlider();
			this.ui_setDatetime();
			this.ui_setActualDatetime();
			this.ui_setNavigation();
			this.ui_setWarning();
		},
		
		clear: function() {
			this.enabled = false;
			this.datetime = null;
			this.actualDatetime = null;
			this.lockTime = false;
			this.requests = new Array();
			this.requestUri = null;
			this.originalUri = null;
			this.requestTimeGateUri = null;
			this.requestMementoUri = null;
			this.document = null;
			this.warningString = '';
			this.first_datetime = null;
			this.last_datetime = null;
			this.next_datetime = null;
			this.prev_datetime = null;
			
		},
		
		parseLinkHeader: function(link) {
			
			var state = 'start';
			var data = link.split('');
			var uri = '';
			var pt = '';
			var pv = '';	
			var d = '';
			
			links = {};	
			while (data.length) {
				if (state == 'start') {
					d = data.shift();
					while (d.match(/\s/)) d = data.shift();
		       		if (d != "<") break
		       		state = "uri";
				} else if (state == "uri") {
		        	uri = '';
			        d = data.shift();
			        while (d != ">") {
			        	uri += d;
			        	d = data.shift();
			        }
			        links[uri] = {};
			        state = "paramstart";
				} else if (state == 'paramstart') {
					d = data.shift();
		           	while (d.match(/\s/) != null) d = data.shift();
		           	if (d == ";") state = 'linkparam';
		           	else if (d == ',') state = 'start';
		           	else break
				} else if (state == 'linkparam') {
					d = data.shift();
					while (d.match(/\s/) != null) d = data.shift();
		           	pt = '';	
		           	while (data.length && d != ' ' && d != '=') {
		           		pt += d;
		           		d = data.shift();
		           	}
		           	while (d.match(/\s/) != null) d = data.shift();
		           	if (d != "=") break
		           	state='linkvalue';
		           	if (links[uri][pt] == undefined) {
		           		links[uri][pt] = new Array();
		           	}
				} else if (state == 'linkvalue') {
					d = data.shift();
		           	while (d.match(/\s/) != null) d = data.shift();
		           	pv = '';
		           	if (d == '"') {
		           		pd = d;
		           		d = data.shift();
		            	while (d != undefined && d != '"' && pd != '\\') {
		            		pv += d;
		            		pd = d;
		            		d = data.shift();
		            	}
		           	} else {
		           		while (d != undefined && d != " " && d != ',' && d != ';') {
		           			pv += d;
		           			d = data.shift();
		           		}
		                if (data.length) data.unshift(d);
		           	}
		           	state = 'paramstart';
		            if (pt == 'rel') links[uri][pt] = links[uri][pt].concat(pv.split(' '));
		            else links[uri][pt].push(pv);
				}
			}
			return links;
		},

		getUriForRel: function(lhash, rel) {
			for (uri in lhash) {
				params = lhash[uri];
				vals = lhash[uri]['rel'];
				if (vals != undefined) {
					for (var v=0, val; val= vals[v]; v++) {
						if (val == rel) {
							return uri;
						}
					}
				}
			}
			return null;
		},
		
		dateToYYYYMMDD: function(date) {
			if (!date) {
				return "";
			}
			yr = date.getUTCFullYear()					
			month = date.getUTCMonth()+1;
			month = month < 10 ? '0' + month : month;
			day = date.getUTCDate();
			day = day < 10 ? '0' + day : day;
			dtstr = '' + yr + '-' + month + '-' + day;	
			return dtstr;
		},

		YYYYMMDDToDate: function(dtstr) {
			yr = dtstr.substring(0, 4);
			month = 11
			day = 31
			hr = 23
			min = 59
			sec = 59
			
			try {
				if (dtstr.length > 4) {
					month = parseInt(dtstr.substr(4,2))-1;  // months are 0-11, stupidly
				}
				if (dtstr.length > 6) {
					day = parseInt(dtstr.substr(6,2));
				}
				if (dtstr.length > 8) {
					hr = parseInt(dtstr.substr(8,2));
				}
				if (dtstr.length > 10) {
					min = parseInt(dtstr.substr(10,2));
				}
				if (dtstr.length > 12) {
					sec = parseInt(dtstr.substr(12,2));
				}
			} catch(err) {
				return null;
			}	
			return new Date(yr, month, day, hr, min, sec);
		},

		fixDate: function(date) {
			date = date.replace('+0000', 'GMT')
			// XXX Terrible, plz fix me
			fyc = date.substr(12,1)
			if (fyc == '0' || (fyc == '1' && date.substr(13,1) != '9')) {
				date = date.substr(0,12) + '20' + date.substring(12, date.length);
			} else if (fyc == '9') {
				date = date.substr(0,12) + '19' + date.substring(12, date.length);
			}
			return date
		},
		
		processLinks: function() {
			lhash = this.parseLinkHeader(link);
			orig = this.getUriForRel(lhash, 'original');

			if (orig) {
				this.originalUri = orig;
				if (this.requestUri && orig != this.requestUri) {
					client.warningString = "TimeGate reported different URI to Original Resource: " + orig + " vs " + this.requestUri
					memState.log(client.warningString);
				} else {
					client.warningString = '';
				}
			}

			// Links to other mementos in VCR UI
			first = this.getUriForRel(lhash, 'first-memento');
			last = this.getUriForRel(lhash, 'last-memento');
			next = this.getUriForRel(lhash, 'next-memento');
			prev = this.getUriForRel(lhash, 'prev-memento');
			
			this.first_datetime = first ? this.fixDate(lhash[first]['datetime'][0]) : null;	
			this.prev_datetime = prev ? this.fixDate(lhash[prev]['datetime'][0]) : null;
			this.next_datetime = next ? this.fixDate(lhash[next]['datetime'][0]) : null;
			this.last_datetime = last ? this.fixDate(lhash[last]['datetime'][0]) : null;

			this.ui_setWarning();
			this.ui_setNavigation();

			// set actual datetime from TimeGate link, can be overridden by Memento
			if (this.requestMementoUri) {
				// step through lhash and find memento
				for (uri in lhash) {
					if (uri == this.requestMementoUri) {
						params = lhash[uri];
						dt = lhash[uri]['datetime'];
						if (dt) {
							client.actualDatetime = this.fixDate(dt[0]);
							client.ui_setActualDatetime();
						}
					}
				}
			}		
		},
		
		ui_setNavigation: function() {
			
			var win = memState.getCurrentWindow();
			fico = win.document.getElementById('mem_first_icon');
			if (this.first_datetime) {
				fico.setAttribute('hidden', false);
				fico.setAttribute('tooltiptext', this.requestUri + "\n @ " + this.first_datetime);
			} else {
				fico.setAttribute('hidden', true);
			}
			
			fico = win.document.getElementById('mem_prev_icon');
			if (this.prev_datetime) {
				fico.setAttribute('hidden', false);
				fico.setAttribute('tooltiptext', this.requestUri + "\n @ " + this.prev_datetime);
			} else {
				fico.setAttribute('hidden', true);
			}

			fico = win.document.getElementById('mem_next_icon');
			if (this.next_datetime) {
				fico.setAttribute('hidden', false);
				fico.setAttribute('tooltiptext', this.requestUri + "\n @ " + this.next_datetime);
			} else {
				fico.setAttribute('hidden', true);
			}
			
			fico = win.document.getElementById('mem_last_icon');
			if (this.last_datetime) {
				fico.setAttribute('hidden', false);
				fico.setAttribute('tooltiptext', this.requestUri + "\n @ " + this.last_datetime);
			} else {
				fico.setAttribute('hidden', true);
			}
		},
			
		ui_setWarning: function() {
			var win = memState.getCurrentWindow();
			var icon = win.document.getElementById("warning_icon");
			if (this.warningString) {
				icon.setAttribute('hidden', false);
				try {
					icon.setAtrribute('tooltiptext', this.warningString);
				} catch (err) {
					memState.log('failed to set tooltiptext' + err)
				}
					
			} else {
				icon.setAttribute('hidden', true);
			}	
		},
		
		ui_setSlider: function() {
			var win = memState.getCurrentWindow();
			var slider = win.document.getElementById("mem_slider");
			var slmax = slider.getAttribute('max');
			if (this.datetime == null) {
				slider.value = 0;
			} else {
				date = new Date(this.datetime);
				diff = date.getTime() - this.startDate.getTime();
				ratio = diff / this.dateDiff;
				ratio *= slmax;
				ratio = Math.ceil(ratio);
				slider.value = ratio;
			}
			slider.blur()
		},
		
		ui_setDatetime: function() {
			var win = memState.getCurrentWindow();
			var dtbox = win.document.getElementById("targetDateLabel");
			if (dtbox.hidden) {
				dtbox = win.document.getElementById("targetDateInput");
			}
			var dtpick = win.document.getElementById("targetDatePicker");
			
			if (this.datetime == null) {
				dtbox.value = this.nowDateStr;
			} else {
				
				date = new Date(this.datetime);
				if (date <= this.startDate) {
					date = this.nowDate;
				}
				if (date) {
					dtstr = this.dateToYYYYMMDD(date);
					dtbox.value = dtstr;
					dtpick.value = dtstr;
				} else {
					dtbox.value = this.nowDateStr;
				}
			}
			dtbox.blur();
			dtpick.blur();
		},
		
		ui_setActualDatetime: function() {
			recWin = memState.getCurrentWindow();

			var mfi = recWin.document.getElementById('actualDate');
			var dbox = recWin.document.getElementById('displayedBox');
			if (!this.actualDatetime) {
				dbox.hidden = true;
			} else {
				dbox.hidden = false;
				date = new Date(this.actualDatetime);
				month = date.getUTCMonth()+1;
				month = month < 10 ? '0' + month : month;
				day = date.getUTCDate();
				day = day < 10 ? '0' + day : day;
				yr = date.getUTCFullYear();
				
				if (memState.locale =='en-US') {
					dtstr = ('' + month + '/' + day + '/' + yr);
				} else {
					dtstr = ('' + day + '/' + month + '/' + yr);			
				}
				mfi.value=dtstr;
			}	
		},
		
		ui_setEnabled: function() {
			recWin = memState.getCurrentWindow();
			var mfi = recWin.document.getElementById('memfox_icon');
			var mfi2 = recWin.document.getElementById('memento_status_icon');
			
			var prefs = Cc["@mozilla.org/preferences-service;1"]
                           .getService(Ci.nsIPrefService)
                           .getBranch("extensions.memento.");
			prefs.QueryInterface(Ci.nsIPrefBranch2);
			
			if (prefs.getCharPref('enabledIconStyle') == 'spin') {
				onicon = 'logo_spin.gif'
			} else {
				onicon = 'logo_on.png'
			}
			
			if (this.enabled) {
				mfi.src = 'chrome://mementofox/content/' + onicon;
				mfi2.src = 'chrome://mementofox/content/' + onicon;
			} else {
				mfi.src = 'chrome://mementofox/content/logo_off2.png';
				mfi2.src = 'chrome://mementofox/content/logo_off2.png';				
			}
			memState.refreshMenubar();
		}
};




// ----------------------  Main TimeMachine XPCOM object

//Main constructor
function MementoFox() {
	// NB This is called during component registration
	// ... and seems to mess things up if we initialize here :(
	// We give it an id for tracking purposes
	this.id = Math.random();
}

// class definition
MementoFox.prototype = {
		// properties required for XPCOM registration:
		classDescription: "MementoFox Javascript XPCOM Component",
		classID:          Components.ID("{488dd930-2209-11df-8a39-0800200c9a66}"),
		contractID:       "@www.mementoweb.org/mementofox;1",
		_xpcom_categories: [{category:'content-policy'}],

		prefs: null,
		mementoWhitelist: [],
		whitelist: [],
		id: 0,
		
		// QueryInterface implementation, e.g. using the generateQI helper (remove argument if skipped steps above)
		QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPolicy, Ci.nsIMementoFox]),
		
		// nsIContentPolicy implementation
		shouldLoad: function(contentType, contentLocation, requestOrigin, aContext, mimeTypeGuess, extra) {
			
			// Only redirect http and https, also called for chrome://  rtsp:// etc
			if (contentLocation.scheme != "http" && contentLocation.scheme != "https") {
				return Ci.nsIContentPolicy.ACCEPT;
			}
			
			// temporary accepts of Mementos to prevent infinite recursion
			idx = this.mementoWhitelist.indexOf(contentLocation.spec);
			if (idx > -1) {
				this.mementoWhitelist.splice(idx,1);
				// Ensure list doesn't explode
				while (this.mementoWhitelist.length > 100) {
					this.mementoWhitelist.splice(0,1);
				}
				
				return Ci.nsIContentPolicy.ACCEPT;
			}
			
			// Check whether host is whitelisted
			if (contentLocation.host in this.whitelist) {
				return Ci.nsIContentPolicy.ACCEPT;
			}
			
			// archive.org also has stub things which are Mementos
			if (contentLocation.host.indexOf('.wstub.archive.org') > -1) {
				return Ci.nsIContentPolicy.ACCEPT;
			}
	
			// Get current request's client			
			client = null;
			if (aContext instanceof Ci.nsIDOMElement) {
				// HTML elem .. step through and find which Chrome has doc's window as content
				if (aContext.ownerDocument && aContext.ownerDocument.defaultView) {
					dv = aContext.ownerDocument.defaultView;
					client = memState.getClientForDocument(dv);	
				}
			} else if (aContext.loadURI) {
				// browser XUL elem's contentWindow
				client = memState.getClientForBrowser(aContext.contentWindow);
				if (aContext.contentWindow && aContext.contentWindow.location != 'about:blank' ) {
					client.document = aContext.contentWindow.top;
				}
			}
			
			// most likely screwed, but get client for most recent window
			if (client == null) {
				w = memState.getCurrentWindow();
				w2 = memState.getMainWindow(w);
				client = memState.getClientForWindow(w2);
			}
			
			// If no client, or disabled, then accept
			if (client == null || !client.enabled || !client.datetime) {
				return Ci.nsIContentPolicy.ACCEPT;			
			} else {
				datetime = client.datetime;
			}
			
			// Accept previous requests
			if (contentLocation.spec in client.requests) {
				return Ci.nsIContentPolicy.ACCEPT;
			}

			// Check if the URI is already rewritten
			// if so, then unrewrite!
			requestedUri = contentLocation.spec;
			mementoUri = null;
			if (contentLocation.spec.indexOf('archive.org/') > -1) {
				// kill first http
				tmpuri = contentLocation.spec.substr(10, contentLocation.spec.length)
				nuridx = tmpuri.indexOf('http://');
				if (nuridx > 1) {
					requestedUri = tmpuri.substr(nuridx, tmpuri.length);
				} else {
					// maybe no http, look for numbers
					var re = new RegExp("http://web\.archive\.org/web/[0-9]+/");
					requestedUri = "http://" + contentLocation.spec.replace(re, "");
				}
				mementoUri = contentLocation.spec;				
			}

			var prefs = memState.prefs;
			
			tgl = prefs.getCharPref("timeGateList");
			toptg = tgl.split(' | ')[0];
			
			// XXX should step through the list, not just use the first
			var timegate = toptg + requestedUri;
			
			// Check original resource for TimeGate link
			var ios = Cc["@mozilla.org/network/io-service;1"]
			             .getService(Ci.nsIIOService, Ci.nsIObserver);
			var ch = ios.newChannel(requestedUri, null, null);
			ch.QueryInterface(Ci.nsIHttpChannel);
			ch.requestMethod = "HEAD";
			ch.open();

			// First check if Original Resource is a Memento
			try {
				link = ch.getResponseHeader("Memento-Datetime");
				// If a real Memento, then is permalink, so accept
				return Ci.nsIContentPolicy.ACCEPT;
			} catch (err) {
				// NS_ERROR_NOT_AVAILABLE = no header, continue
			}
			
			try {
				link = ch.getResponseHeader("Link");
				lhash = client.parseLinkHeader(link);
				tg = client.getUriForRel(lhash, 'timegate')
				if (tg) {
					// tg could be null if Link header exists, and no rel=timegate
					timegate = tg;
				}
			} catch (err) {
				// NS_ERROR_NOT_AVAILABLE if header not set\
			}
		
			this.mementoWhitelist.push(timegate);
			
	    	// Query TimeGate    
			if (!aContext || !aContext.loadURI) {
				// aContext is an HTML elem, need to do work ourselves
		
				var where = timegate;
				var status = 0;
				while (true) {
					var ch = ios.newChannel(where, null, null);                  
					ch.QueryInterface(Ci.nsIHttpChannel);
					ch.requestMethod = "HEAD";
					ch.setRequestHeader("Accept-Datetime", datetime, false);
					ch.open();	
					status = ch.responseStatus;
					if (status >= 300 && status < 400) {
						try {
							where = ch.getResponseHeader("Location");
						} catch (err) {
							break;
						}
					} else {						
						// Put back internet archive link, if we unrewrote it
						if (mementoUri) {
							status = 200;
							where = mementoUri;
						}
						break;
					}
				}

				if (status >= 400) {				
					if (prefs.getBoolPref('loadCurrent')) {
						// tried to find, failed. Just load current
						return Ci.nsIContentPolicy.ACCEPT;
					} else {
						return Ci.nsIContentPolicy.REJECT_REQUEST;
					}
				}
		
				if (where == contentLocation.spec) {
					// we were redirected back to the original, so accept
					return Ci.nsIContentPolicy.ACCEPT;
				}
		
				this.mementoWhitelist.push(where);
		
				if (aContext instanceof Ci.nsIDOMElement) {

					if (aContext.localName.toLowerCase() == 'script') {
						// This seems to avoid XSS somehow! YMMV
						contentLocation.spec = where;
						return Ci.nsIContentPolicy.ACCEPT;						
					} else  {
						var timer = Cc["@mozilla.org/timer;1"]
		                               .createInstance(Ci.nsITimer);
						timer.init({observe: function() {
							var elm = aContext.ownerDocument.createElement(aContext.localName);
							attribs = aContext.attributes;
							for (var i=0; i < attribs.length ; i++) {
								item = attribs.item(i);	
								if (item.name == 'src' || item.name == 'href') {
									elm.setAttribute(item.name, where);
								} else {
									elm.setAttribute(item.name, item.value);
								}
							}
							if (aContext.parentNode) {
								aContext.parentNode.replaceChild(elm, aContext);
							} else {
								return Ci.nsIContentPolicy.ACCEPT;								
							}
						}}, 0, timer.TYPE_ONE_SHOT);
					} 
				}
				return Ci.nsIContentPolicy.REJECT_REQUEST;
		
			} else {
				// Top level resource, just follow nose				
				// check TG for error
				if (prefs.getBoolPref('loadCurrent')) {
					var ch = ios.newChannel(timegate, null, null);                  
					ch.QueryInterface(Ci.nsIHttpChannel);
					ch.requestMethod = "HEAD";
					ch.setRequestHeader("Accept-Datetime", datetime, false);
					ch.open();	
					status = ch.responseStatus;
					if (status >= 400) {
						// tried to find, failed. Just load current
						return Ci.nsIContentPolicy.ACCEPT;
					}
				}
				
				client.requestUri = requestedUri;
				client.requestTimeGateUri = timegate;
				client.requests.push(timegate);
				// Force reload via redirecting TG
				aContext.loadURI(timegate, null, null);
				return Ci.nsIContentPolicy.REJECT_REQUEST;
			}
		},

		shouldProcess: function(contentType, contentLocation, requestOrigin, insecNode, mimeType, extra) {
			// This is post-processing 
			return Ci.nsIContentPolicy.ACCEPT;
		},

		observe: function(subject, topic, data) { 
			// observed events are added in start()
			
			// Refresh on changed UI preferences
			if (topic == "nsPref:changed") {
				refList = ['displaySlider', 'displayPicker', 'displayText', 'hideWhenInactive', 'hideToolbarWhenInactive'];			
				if (refList.indexOf(data) > -1) {
					memState.refreshMenubar();
				}
				return;
			} 

			// Otherwise we're observing an HTTP connection
			var channel = subject.QueryInterface(Ci.nsIHttpChannel);
			
			if (channel.requestMethod != "GET") {		
				return;
			}
					
			client = null;
			for (var i=0,cl; cl=memState.allClients[i]; i++) {
				cl = cl[1];
				if (channel.originalURI && cl.requestTimeGateUri == channel.originalURI.spec) {
					client = cl;
					break;
				} else if (channel.referrer && cl.requestMementoUri == channel.referrer.spec) {
					client = cl;
					break; 
				}	
			}
		
			// Couldn't find it based on our URI, hopefully in main window
			if (!client) {
				w = memState.getCurrentWindow();
				window = memState.getMainWindow(w);
				client = memState.getClientForWindow(window);
			}
			
			if (!client || !client.enabled || !client.datetime) {
				return;
			}
			
			// Intercept HTTP requests and responses
			if (topic == "http-on-modify-request") {     

				adthdr = client.datetime;
				// Add in our experimental bubble
				var prefs = memState.prefs;
				if (prefs.getBoolPref('showBubble')) {					
					y = prefs.getIntPref('bubbleYears');
					m = prefs.getIntPref('bubbleMonths');
					d = prefs.getIntPref('bubbleDays');
					h = prefs.getIntPref('bubbleHours');
					mm = prefs.getIntPref('bubbleMinutes');
					s = prefs.getIntPref('bubbleSeconds');
					if (y || m || d || h || mm || s) {
						extra = ";P";
						if (y) {
							extra += y + "Y";
						}
						if (m) {
							extra += m + "M";
						}
						if (d) {
							extra += d + "D";
						}
						if (h || mm || s) {
							extra += "T";
							if (h) {
								extra += h + "H";
							} 
							if (mm) {
								extra += mm + "M";
							}
							if (s) {
								extra += s + "S";
							}
						}
						adthdr += extra;
					}
				}
				channel.setRequestHeader("Accept-Datetime", adthdr, false);            
			} else if (topic == 'http-on-examine-response') {
				
				// Detect TimeGate response
				var uri = channel.originalURI.spec;
				var isTimeGate = false;
				var memento = null;
		
				// At Memento that sends Memento-Datetime
				try {
					contentDatetime = channel.getResponseHeader("Memento-Datetime");
					if (uri == client.requestTimeGateUri) {
						// Use archive's datetime, rather than timegate's
						client.requestMementoUri = channel.URI.spec;
						client.actualDatetime = client.fixDate(contentDatetime);
						client.ui_setActualDatetime();
					}	
				} catch (err) {}
		
				// Properly behaving timegate will send Vary
				try {
					vary = channel.getResponseHeader("Vary");
					vary = vary.toLowerCase();
					isTimeGate = vary.indexOf('datetime') > -1;
					if (isTimeGate) {
						try {
							memento = channel.getResponseHeader('Location');
							client.requestMementoUri = memento;
							this.mementoWhitelist.push(memento);
						} catch (err) {} 
						// and remove request
						idx = client.requests.indexOf(uri);
						if (idx > -1) {
							client.requests.splice(idx, 1);
						}
					}
				} catch (err) {}
		
				// Now we only care about timegate response
				if (uri != client.requestTimeGateUri) {
					if (isTimeGate) {
						memState.log('Detected timegate not at requestTimeGateUri: ' + uri + ' vs ' + client.requestTimeGateUri);
					}
					return;
				}
		
				// Process Link header
				try {
					link = channel.getResponseHeader("Link");		
					client.processLinks(link);
				} catch (err) {} // No Link header
			}
		},

		
		// Called from menubar overlay javascript
		start: function() {
			
			memState.log("Starting Time Machine")
			var os = Cc["@mozilla.org/observer-service;1"]
			                            .getService(Ci.nsIObserverService);
			os.addObserver(this, "http-on-modify-request", false); 
			os.addObserver(this, "http-on-examine-response", false);
			
			var prefs = memState.prefs;
			prefs.QueryInterface(Ci.nsIPrefBranch2);
			prefs.addObserver("", this, false);
			
			var whitelist = [];
			whitelist.push("web.archive.org");
			whitelist.push('webcitation.org');
			whitelist.push('www.webcitation.org');
			whitelist.push('wayback.archive-it.org');
			whitelist.push('memento.waybackmachine.org');
			whitelist.push('www.google-analytics.com');   // unnecessarily wastes time

			this.whitelist = whitelist;
			this.mementoWhitelist = [];		
		},

		toggleState: function(chk) {
			// Set target date/time    	
			window = memState.getCurrentWindow();
			w = memState.getMainWindow(window)
			client = memState.getClientForWindow(w);

			if (client) {
				client.enabled = !client.enabled;
				client.ui_setEnabled();
				if (!client.enabled) {
					this.reloadWindow()
					client.clear();
					client.switchTo();
				} else if (client.datetime && client.datetime > client.startDate) {
					this.reloadWindow();
				}
			}
		},

		setSlider: function(value) {
			window = memState.getCurrentWindow();
			w = memState.getMainWindow(window)
			client = memState.getClientForWindow(w);

			if (client && !client.lockTime) {
				var slider = window.document.getElementById("mem_slider");
				slmax = slider.getAttribute('max');
				mult = client.dateDiff / slmax;
				t = (value * mult) + client.startDate.getTime();
				cdt = new Date(t);
				dt.setUTCHours(12);
				dt.setUTCMinutes(0);
				cdt = cdt.toUTCString();
				client.datetime = cdt;
				client.ui_setDatetime();
			}
		},

		fixSlider: function(value) {
			window = memState.getCurrentWindow();
			w = memState.getMainWindow(window)
			client = memState.getClientForWindow(w);
			if (client) {
				var slider = window.document.getElementById("mem_slider");
				slmax = slider.getAttribute('max');
				mult = client.dateDiff / slmax;
				t = (value * mult) + client.startDate.getTime();
				try {
					dt = new Date(t);
					dt.setUTCHours(12);
					dt.setUTCMinutes(0);
					dt.setUTCSeconds(0);
					dt = dt.toUTCString();
				} catch(err) {
					dt = null;
				}
				client.datetime = dt;
				client.ui_setDatetime();
				if (!client.enabled) {
					client.enabled = true;
					client.ui_setEnabled();
				}
				this.reloadWindow();
			}
		},

		setDatetime: function(dts) {
			// Set target date/time

			window = memState.getCurrentWindow();
			w = memState.getMainWindow(window)
			client = memState.getClientForWindow(w);

			if (client) {				
				client.datetime = dts;
				client.lockTime = true;
				client.ui_setDatetime();
				client.ui_setSlider();
				client.enabled = dts ? true : false;
				client.ui_setEnabled();
				client.lockTime = false;
			}
			this.reloadWindow();

		},

		getWarning: function() {
			window = memState.getCurrentWindow();
			w = memState.getMainWindow(window)
			client = memState.getClientForWindow(w);
			return client.warningString;
		},

		reloadWindow: function() {
			// check first if we have an originalURI
			window = memState.getCurrentWindow();
			if (window) {
				w = memState.getMainWindow(window)
				client = memState.getClientForWindow(w);
				var uri = null;
				if (client.requestUri) {
					uri = client.requestUri;
				} else {
					uri = memState.getCurrentUri();
				}
				if (uri) {
					try {
						window.content.location = uri;  
					} catch (err) {
						// REJECT will generate an error, even though we do refresh to somewhere else
					}
				}
			}
		}
};

// List of components to register
var components = [MementoFox];

// Register components
function NSGetModule(compMgr, fileSpec) {
  return XPCOMUtils.generateModule(components);
}
